package com.xdb.sqlparser.dialect.mysql.parser;

import com.xdb.sqlparser.ast.SQLExpr;
import com.xdb.sqlparser.ast.SQLSetQuantifier;
import com.xdb.sqlparser.ast.expr.SQLIdentifierExpr;
import com.xdb.sqlparser.ast.expr.SQLLiteralExpr;
import com.xdb.sqlparser.ast.statement.SQLSelectGroupByClause;
import com.xdb.sqlparser.ast.statement.SQLSelectQuery;
import com.xdb.sqlparser.ast.statement.SQLSelectQueryBlock;
import com.xdb.sqlparser.ast.statement.SQLTableSource;
import com.xdb.sqlparser.ast.statement.SQLUnionQuery;
import com.xdb.sqlparser.dialect.mysql.ast.MySqlForceIndexHint;
import com.xdb.sqlparser.dialect.mysql.ast.MySqlIgnoreIndexHint;
import com.xdb.sqlparser.dialect.mysql.ast.MySqlIndexHint;
import com.xdb.sqlparser.dialect.mysql.ast.MySqlIndexHintImpl;
import com.xdb.sqlparser.dialect.mysql.ast.MySqlUseIndexHint;
import com.xdb.sqlparser.dialect.mysql.ast.expr.MySqlOutFileExpr;
import com.xdb.sqlparser.dialect.mysql.ast.statement.MySqlSelectGroupBy;
import com.xdb.sqlparser.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.xdb.sqlparser.dialect.mysql.ast.statement.MySqlUnionQuery;
import com.xdb.sqlparser.dialect.mysql.ast.statement.MySqlSelectQueryBlock.Limit;
import com.xdb.sqlparser.dialect.sql99.parser.SQL99ExprParser;
import com.xdb.sqlparser.dialect.sql99.parser.SQL99SelectParser;
import com.xdb.sqlparser.exception.ParserException;
import com.xdb.sqlparser.parser.SQLToken;

public class MySqlSelectParser extends SQL99SelectParser {

	public MySqlSelectParser(SQL99ExprParser exprParser) {
		super(exprParser);
	}

	public MySqlSelectParser(String sql) {
		this(new MySqlExprParser(sql));
	}

	@Override
	public SQLSelectQuery query() {
		if (lexer.token() == (SQLToken.LPAREN)) {
			lexer.nextToken();

			SQLSelectQuery select = query();
			accept(SQLToken.RPAREN);

			return queryRest(select);
		}

		MySqlSelectQueryBlock queryBlock = new MySqlSelectQueryBlock();

		if (lexer.token() == SQLToken.SELECT) {
			lexer.nextToken();

			if (lexer.token() == SQLToken.HINT) {
				this.exprParser.parseHints(queryBlock.getHints());
			}

			if (lexer.token() == SQLToken.COMMENT) {
				lexer.nextToken();
			}

			if (lexer.token() == (SQLToken.DISTINCT)) {
				queryBlock.setDistionOption(SQLSetQuantifier.DISTINCT);
				lexer.nextToken();
			} else if (identifierEquals("DISTINCTROW")) {
				queryBlock.setDistionOption(SQLSetQuantifier.DISTINCTROW);
				lexer.nextToken();
			} else if (lexer.token() == (SQLToken.ALL)) {
				queryBlock.setDistionOption(SQLSetQuantifier.ALL);
				lexer.nextToken();
			}

			if (identifierEquals("HIGH_PRIORITY")) {
				queryBlock.setHignPriority(true);
				lexer.nextToken();
			}

			if (identifierEquals("STRAIGHT_JOIN")) {
				queryBlock.setStraightJoin(true);
				lexer.nextToken();
			}

			if (identifierEquals("SQL_SMALL_RESULT")) {
				queryBlock.setSmallResult(true);
				lexer.nextToken();
			}

			if (identifierEquals("SQL_BIG_RESULT")) {
				queryBlock.setBigResult(true);
				lexer.nextToken();
			}

			if (identifierEquals("SQL_BUFFER_RESULT")) {
				queryBlock.setBufferResult(true);
				lexer.nextToken();
			}

			if (identifierEquals("SQL_CACHE")) {
				queryBlock.setCache(true);
				lexer.nextToken();
			}

			if (identifierEquals("SQL_NO_CACHE")) {
				queryBlock.setCache(false);
				lexer.nextToken();
			}

			if (identifierEquals("SQL_CALC_FOUND_ROWS")) {
				queryBlock.setCalcFoundRows(true);
				lexer.nextToken();
			}

			parseSelectList(queryBlock);

			parseInto(queryBlock);
		}

		parseFrom(queryBlock);

		parseWhere(queryBlock);

		parseGroupBy(queryBlock);

		queryBlock.setOrderBy(this.exprParser.parseOrderBy());

		if (lexer.token() == SQLToken.LIMIT) {
			queryBlock.setLimit(parseLimit());
		}

		if (lexer.token() == SQLToken.PROCEDURE) {
			lexer.nextToken();
			throw new ParserException("TODO");
		}

		parseInto(queryBlock);

		if (lexer.token() == SQLToken.FOR) {
			lexer.nextToken();
			accept(SQLToken.UPDATE);

			queryBlock.setForUpdate(true);
		}

		if (lexer.token() == SQLToken.LOCK) {
			lexer.nextToken();
			accept(SQLToken.IN);
			acceptIdentifier("SHARE");
			acceptIdentifier("MODE");
			queryBlock.setLockInShareMode(true);
		}

		return queryRest(queryBlock);
	}

	protected void parseInto(SQLSelectQueryBlock queryBlock) {
		if (lexer.token() == (SQLToken.INTO)) {
			lexer.nextToken();

			if (identifierEquals("OUTFILE")) {
				lexer.nextToken();

				MySqlOutFileExpr outFile = new MySqlOutFileExpr();
				outFile.setFile(expr());

				queryBlock.setInto(outFile);

				if (identifierEquals("FIELDS") || identifierEquals("COLUMNS")) {
					lexer.nextToken();

					if (identifierEquals("TERMINATED")) {
						lexer.nextToken();
						accept(SQLToken.BY);
					}
					outFile.setColumnsTerminatedBy((SQLLiteralExpr) expr());

					if (identifierEquals("OPTIONALLY")) {
						lexer.nextToken();
						outFile.setColumnsEnclosedOptionally(true);
					}

					if (identifierEquals("ENCLOSED")) {
						lexer.nextToken();
						accept(SQLToken.BY);
						outFile.setColumnsEnclosedBy((SQLLiteralExpr) expr());
					}

					if (identifierEquals("ESCAPED")) {
						lexer.nextToken();
						accept(SQLToken.BY);
						outFile.setColumnsEscaped((SQLLiteralExpr) expr());
					}
				}

				if (identifierEquals("LINES")) {
					lexer.nextToken();

					if (identifierEquals("STARTING")) {
						lexer.nextToken();
						accept(SQLToken.BY);
						outFile.setLinesStartingBy((SQLLiteralExpr) expr());
					} else {
						identifierEquals("TERMINATED");
						lexer.nextToken();
						accept(SQLToken.BY);
						outFile.setLinesTerminatedBy((SQLLiteralExpr) expr());
					}
				}
			} else {
				queryBlock.setInto(this.exprParser.name());
			}
		}
	}

	protected void parseGroupBy(SQLSelectQueryBlock queryBlock) {
		SQLSelectGroupByClause groupBy = null;

		if (lexer.token() == SQLToken.GROUP) {
			groupBy = new SQLSelectGroupByClause();

			lexer.nextToken();
			accept(SQLToken.BY);

			while (true) {
				groupBy.addItem(this.getExprParser().parseSelectGroupByItem());
				if (!(lexer.token() == (SQLToken.COMMA))) {
					break;
				}
				lexer.nextToken();
			}

			if (lexer.token() == SQLToken.WITH) {
				lexer.nextToken();
				acceptIdentifier("ROLLUP");

				MySqlSelectGroupBy mySqlGroupBy = new MySqlSelectGroupBy();
				for (SQLExpr sqlExpr : groupBy.getItems()) {
					mySqlGroupBy.addItem(sqlExpr);
				}
				mySqlGroupBy.setRollUp(true);

				groupBy = mySqlGroupBy;
			}
		}

		if (lexer.token() == SQLToken.HAVING) {
			lexer.nextToken();

			if (groupBy == null) {
				groupBy = new SQLSelectGroupByClause();
			}
			groupBy.setHaving(this.exprParser.expr());
		}

		queryBlock.setGroupBy(groupBy);
	}

	protected SQLTableSource parseTableSourceRest(SQLTableSource tableSource) {
		if (identifierEquals("USING")) {
			return tableSource;
		}

		if (lexer.token() == SQLToken.USE) {
			lexer.nextToken();
			MySqlUseIndexHint hint = new MySqlUseIndexHint();
			parseIndexHint(hint);
			tableSource.getHints().add(hint);
		}

		if (identifierEquals("IGNORE")) {
			lexer.nextToken();
			MySqlIgnoreIndexHint hint = new MySqlIgnoreIndexHint();
			parseIndexHint(hint);
			tableSource.getHints().add(hint);
		}

		if (identifierEquals("FORCE")) {
			lexer.nextToken();
			MySqlForceIndexHint hint = new MySqlForceIndexHint();
			parseIndexHint(hint);
			tableSource.getHints().add(hint);
		}

		return super.parseTableSourceRest(tableSource);
	}

	private void parseIndexHint(MySqlIndexHintImpl hint) {
		if (lexer.token() == SQLToken.INDEX) {
			lexer.nextToken();
		} else {
			accept(SQLToken.KEY);
		}

		if (lexer.token() == SQLToken.FOR) {
			lexer.nextToken();

			if (lexer.token() == SQLToken.JOIN) {
				lexer.nextToken();
				hint.setOption(MySqlIndexHint.Option.JOIN);
			} else if (lexer.token() == SQLToken.ORDER) {
				lexer.nextToken();
				accept(SQLToken.BY);
				hint.setOption(MySqlIndexHint.Option.ORDER_BY);
			} else {
				accept(SQLToken.GROUP);
				accept(SQLToken.BY);
				hint.setOption(MySqlIndexHint.Option.GROUP_BY);
			}
		}

		accept(SQLToken.LPAREN);
		if (lexer.token() == SQLToken.PRIMARY) {
			lexer.nextToken();
			hint.getIndexList().add(new SQLIdentifierExpr("PRIMARY"));
		} else {
			this.exprParser.names(hint.getIndexList());
		}
		accept(SQLToken.RPAREN);
	}

	protected MySqlUnionQuery createSQLUnionQuery() {
		return new MySqlUnionQuery();
	}

	public SQLUnionQuery unionRest(SQLUnionQuery union) {
		if (lexer.token() == SQLToken.LIMIT) {
			MySqlUnionQuery mysqlUnionQuery = (MySqlUnionQuery) union;
			mysqlUnionQuery.setLimit(parseLimit());
		}
		return super.unionRest(union);
	}

	public Limit parseLimit() {
		return ((MySqlExprParser) this.exprParser).parseLimit();
	}

	public MySqlExprParser getExprParser() {
		return (MySqlExprParser) exprParser;
	}
}
